package apis

import (
	"net/http"
	"libs"
	"github.com/google/uuid"
	"config"
	"log"
)

/*
生产消息逻辑：
1. 根据订阅关系确定需要写入的队列
2. 写入object值名为consumer_id:topic的队列
 */
func Producer(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	// 请求签名校验
	if libs.ValidSignature(r) == false {
		JsonResponse(w, 403, "签名校验失败", "")
		return
	}
	// 请求参数校验
	params, err := validParameters(r.Form, "topic object")
	if err != nil {
		JsonResponse(w, 400, err.Error(), "")
		return
	}
	// 写日志
	log.Printf(`%s?version=%d&app_id=%s&topic=%s`, r.URL.Path, params.Version, params.AppId, params.Topic)
	// 根据订阅关系处理数据
	subs, ok := config.RelationMap[params.AppId + ":" + params.Topic]
	if !ok {
		JsonResponse(w, 449, "no subscriber", "")
		return
	}
	commands := make([]*libs.MultiLine, 0)
	for _, sub := range subs {
		qName := sub + ":" + params.Topic
		commands = append(
			commands,
			libs.NewMultiLine("RPUSH", qName, params.Object),
		)
		libs.TopicMap.Set(qName)
	}
	if _, err := libs.MultiExec(commands); err != nil {
		log.Println(err)
		libs.CallDingWebHook("Redis故障告警",  "*Redis写入失败*\n- 错误信息：" + err.Error())
		JsonResponse(w, 500, "redis error", "")
	} else {
		JsonResponse(w, 200, "", "created")
	}
}

/*
消费队列的逻辑：
1. 从consumer_id:topic队列的左侧取出并移除一个数据data
2. 创建确认消费的token
3. 设置一个名为token:consumer_id:topic的key，并设置过期时间timeout
4. 在名为consumer_id:topic:__tmp__的hash表中设置key为token值为data的一条记录
 */
func Consumer(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	// 请求签名校验
	if libs.ValidSignature(r) == false {
		JsonResponse(w, 403, "签名校验失败", "")
		return
	}
	// 请求参数校验
	params, err := validParameters(r.Form, "topic timeout limit")
	if err != nil {
		JsonResponse(w, 400, err.Error(), "")
		return
	}
	// 写日志
	log.Printf(`%s?version=%d&app_id=%s&topic=%s`, r.URL.Path, params.Version, params.AppId, params.Topic)
	// 取数据 TODO redis不可用时可能造成数据丢失
	qName := params.AppId + ":" + params.Topic
	objects := make([]Response, 0)
	for i := 0; i < params.Limit; i++ {
		object, err := libs.LPop(qName)
		if err != nil {
			if err == libs.ErrNil {
				break
			} else {
				log.Println(err)
				JsonResponse(w, 500, "redis error", "")
				return
			}
		}
		// 设置中间数据，用于确认消费超时后重新返回队列
		token := uuid.New().String()
		objects = append(objects, Response{Token: token, Topic: params.Topic, Object: object})
		commands := []*libs.MultiLine{
			libs.NewMultiLine("SETEX", token + ":" + qName, params.Timeout, "1"),
			libs.NewMultiLine("HSET", qName + ":__tmp__", token, object),
		}
		if _, err := libs.MultiExec(commands); err != nil {
			log.Println(err)
			JsonResponse(w, 500, "redis error", "")
			return
		}
	}
	JsonResponse(w, 200, "", objects)
}

/*
确认消费逻辑：
1. 删除名为token:consumer_id:topic的KEY
2. 删除名为consumer_id:topic:__tmp__的hash表中key为token的记录
 */
func Callback(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	// 请求签名校验
	if libs.ValidSignature(r) == false {
		JsonResponse(w, 403, "签名校验失败", "")
		return
	}
	// 请求参数校验
	params, err := validParameters(r.Form, "topic token")
	if err != nil {
		JsonResponse(w, 400, err.Error(), "")
		return
	}
	// 写日志
	log.Printf(`%s?version=%d&app_id=%s&topic=%s`, r.URL.Path, params.Version, params.AppId, params.Topic)
	// 删除数据
	qName := params.AppId + ":" + params.Topic
	commands := []*libs.MultiLine {
		libs.NewMultiLine("DEL", params.Token + ":" + qName),
		libs.NewMultiLine("HDEL", qName + ":__tmp__", params.Token),
	}
	if _, err := libs.MultiExec(commands); err != nil {
		log.Println(err)
		JsonResponse(w, 500, "redis error", "")
		return
	}
	JsonResponse(w, 200, "", "deleted")
}
